/*
 License
 
 Copyright © 2012 Kevin Kimmich
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights to 
 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 the Software, and to permit persons to whom the Software is furnished to do so, 
 subject to the following conditions:
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
 FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 @file
 This is a chunk based dynamic buffer implementation.
 */
#ifndef _stream_h
#define _stream_h

#ifdef __cplusplus
extern "C" {
#endif

//Make sure the chunk size will roll over cleanly.
#define DEFAULT_CHUNK_SIZE (8192)
#define START_LENGTH (0x0)

/**
 Initializer for stream_init().
 */
#define UNBOUNDED (0xFFFFFFFF)

typedef enum io_result { io_error=-1,io_null=0,io_timeout=1,io_would_block=2,io_shutdown=3,io_ok=4,io_closed=5 } io_result;

struct stream_event
{
	pthread_cond_t signal;
	io_result result;
	int bytes;
};

typedef struct stream_event stream_event;

struct stream
{
	list_t chunks;
	uint32_t reader_length;
	uint32_t writer_length;
	uint32_t allocated;
	uint32_t max_size;
	pthread_mutex_t stream_mutex;

	stream_event read_event;
	stream_event write_event;
};

typedef struct stream stream;

#define STREAM_INITIALIZER(size) { .chunks=LIST_INITIALIZER, .reader_length=0, .writer_length=0, .allocated=0, .max_size=size, .stream_mutex=PTHREAD_MUTEX_INITIALIZER, {.signal=PTHREAD_COND_INITIALIZER}, {.signal=PTHREAD_COND_INITIALIZER }}


//Stream internals
typedef struct chunk_node_t
{
	list_node_t node;
	void* data;
} chunk_node_t;

chunk_node_t* chunk_malloc( void );
__inline__ void lock_stream(stream* s);
__inline__ void unlock_stream(stream* s);

//Public Functions

/**
 Free a chunk allocated by the stream.
 @param chunk The chunk to free.
 */
void chunk_free( chunk_node_t* chunk );

/**
 Return the first chunk in the stream.
 @param s The stream.
 @param len Returns the length of data in the chunk.
 @param last After the call is set to 1 if it's the last chunk in the stream.
 */
chunk_node_t* stream_to_chunk(stream* s, int* len, int* last);

/**
 Initialize a stream structure.
 @param s The stream.
 @param max_size The maximum size for the stream. For blocking streams, set max_size to some value smaller than UNBOUNDED.
 */
void stream_init( stream* s, uint32_t max_size );

/**
 Cleanup the stream.
 */
void stream_cleanup( stream* s );
/**
 Really the same as stream_cleanup(), but
 the intention is different.
 */
void stream_flush(stream* s);

/**
 Return the length of the stream. NOT thread safe
 potential race condition.
 */
uint32_t stream_length( stream* s );

/**
 Create an identical copy of the stream. Any data in dst is lost.
 @param src The source stream.
 @param dst The copy of the stream.
 */
int stream_duplicate(stream* src, stream* dst);

/**
 Read data into the stream.
 @param s The stream.
 @param s data A pointer to the data to read into the stream.
 @param length The length of the data to read into the stream.
 */
int stream_from_memory( stream* s, void* data, uint32_t length);

/**
 Read from the stream into a buffer. Returns the length of data actually read.
 @param s The stream.
 @param s data A pointer to the buffer for data from the stream.
 @param length The length of the data to read from the stream.
 */
int stream_to_memory(stream* s, void* data, uint32_t length);

/**
 Convert the stream into a null terminated string.
 @param s The stream.
 @param len The length of the stream, set to NULL if not interested.
 */
char* stream_to_string(stream* s, int* len);

/**
 Convert the stream into a null terminated string, but keep the contents in the stream.
 @param s The stream.
 @param len The length of the stream.
 */
char* stream_to_string_keep(stream* s, int* len);

/**
 Convert the stream into a buffer.
 @param s The stream.
 @param len The length of the buffer contents.
 */
void* stream_to_buffer(stream* s, int* len);

/**
 Like sprintf().
 @param s The stream.
 @param format The format string.
 @... Parameters for the format.
 */
int stream_printf(stream* s, const char* format, ...);

/**
 Like vprintf().
 @param s The stream.
 @param format The format string.
 @param va_list Parameters for the format.
 */
int stream_vprintf(stream* s,const char* format,va_list list);

/**
 Like strcmp().
 @param a Compare stream a.
 @param b Compare stream b.
 */
int stream_strcmp(stream* a, stream* b);

/**
 Read the contents of src stream to dst stream.
 @param dst The destination stream.
 @param src The source stream.
 */
int stream_to_stream(stream* dst, stream* src);

/**
 Compress stream src into dst.
 @param src The source stream.
 @param dst The destination stream.
 */
int stream_to_gzip(stream* dst, stream* src);

/**
 Stream length bytes from src to dst.
 @param dst The destination stream.
 @param src The source stream.
 @param length The length of bytes to stream.
 */
int stream_to_stream_length(stream* dst, stream* src, uint32_t length);

/**
 Like strstr().
 */
int stream_strstr(stream* s, const char* needle);

/**
 Read an XML doc into a stream.
 @param s The stream.
 @param doc The libxml2 document.
 */
int doc_to_stream(stream* s, xmlDocPtr doc);

/**
 Read a stream into an XML document.
 @param s The stream.
 */
xmlDocPtr stream_to_doc(stream* s);

/**
 Read the contents of a node into a stream.
 @param s The stream.
 @param node The node to read into the stream.
 @param stream_siblings Set to 1 to read out the siblings of the node. The children of the node are always streamed.
 */
int node_to_stream(stream* s, xmlNodePtr node, int stream_siblings);
/**
 Read the contents of a node into a stream.
 @param s The stream.
 @param node The node to stream.
 @param no_right Only stream the left side of the node. For example <a>A</a> will only stream <a>A if no_right is set.
 */
int node_to_stream_left(stream* s, xmlNodePtr node, int no_right);
/**
 Read the right portion of the node into the stream. For example if the node is <a>A</a>, only </a> is read into the stream.
 @param s The stream.
 @param node The libxml2 node.
 */
int node_to_stream_right(stream* s, xmlNodePtr node);

/**
 Convert a stream contents into a libxml2 node.
 @param s The stream.
 */
xmlNodePtr stream_to_node(stream* s);


//I/O Functions

/**
 Write the contents of a stream to a file with 'filename'.
 @param s The stream.
 @param filename The filename.
 */
int stream_from_filename(stream* s, const char* filename);
/**
 Write the contents of a stream to a file with 'filename'.
 @param s The stream.
 @param filename The filename.
 */
int stream_to_filename(stream* s, const char* filename);

//This set of functions is mostly for asynchronous I/O.

/**
 Read from a file descriptor into a stream.
 @param s The stream.
 @param fd The file descriptor.
 @param res The result of the read.
 @param bytes_received The bytes received.
 */
void stream_from_file(stream* s, int fd, io_result* res, int* bytes_received);

/**
 Write to a file descriptor from a stream.
 @param s The stream.
 @param fd The file descriptor.
 @param res The result of the write.
 @param bytes_sent The bytes sent.
 */
void stream_to_file(stream* s, int fd, io_result* res, int* bytes_sent);

/**
 Read from a socket into a stream.
 @param s The stream.
 @param fd The socket file descriptor.
 @param result The result of the recv.
 @param bytes_received The bytes received.
 */
void stream_from_socket(stream* s, int fd, io_result* result, int* bytes_received);

/**
 Read from a SSL socket into a stream.
 @param s The stream.
 @param fd The socket file descriptor.
 @param result The result of the recv.
 @param bytes_received The bytes received.
 */
void stream_from_ssl_socket(stream* s, SSL* ssl, io_result* result, int* bytes_received);

/**
 Write to a socket from a stream.
 @param s The stream.
 @param fd The socket file descriptor.
 @param result The result of the send.
 @param bytes_received The bytes sent.
 */
void stream_to_socket(stream* s, int fd,io_result* result,int* bytes_sent);
void stream_to_ssl_socket(stream* s, SSL* ssl,io_result* result,int* bytes_sent);

//Blocking functions--very handy for thread to thread communication. Generally, these
//functions are much easier to use for I/O operations than the asynchronous I/O stream functions.
//That is, create a reader thread, a writer thread, then use blocking sockets to recv and send.
//NOTE!!! These functions are meant to have ONE READER THREAD and ONE WRITER THREAD.

/**
 Try to read length bytes from the stream. This WILL BLOCK
 until the bytes are fully available. There should be only one reader thread.
 @param s The stream.
 @param data a buffer to read into.
 @param length The bytes to wait for.
 */
int stream_blocking_read(stream* s, void* data, uint32_t length);

/**
 Move the contents of src stram to dst.
 @param dst The destination.
 @param src The source.
 */
int stream_to_blocking_stream( stream* dst, stream* src );

/**
 Try to write length bytes from the stream. This WILL BLOCK
 until the capacity is fully available in the stream. There should
 be only one writer thread.
 @param s The stream.
 @param data a buffer to write from.
 @param length The bytes to write.
 */
int stream_blocking_write(stream* s, void* data, uint32_t length);

/**
 Try to read length bytes from the stream. This WILL BLOCK
 until the bytes are fully available or the timeout expires (return -1). There should be only one reader thread.
 @param s The stream.
 @param data a buffer to read into.
 @param length The bytes to wait for.
 */
int stream_blocking_read_timeout(stream* s, void* data, uint32_t length,uint32_t msec);

/**
 Try to write length bytes from the stream. This WILL BLOCK
 until the capacity is fully available in the stream or the timeout expires (return -1). There should only be one writer thread.
 @param s The stream.
 @param data a buffer to write from.
 @param length The bytes to write.
 */
int stream_blocking_write_timeout(stream* s, void* data, uint32_t length,uint32_t msec);

/**
 Cleanup the stream, and signal the reader and writer that
 the stream has been flushed.
 @param s The stream.
 */
void stream_blocking_cleanup(stream* s);

#ifdef __cplusplus
}
#endif


#endif
